// eslint-disable-next-line import/named
import { ShapeStatus, ShapeAnimationStatus, LineCapType, LineJoinType } from '../declare/enum';
import {
  AbstractShapeAttribute,
  AbstractShapeOptions,
  CanvasContext2DAttribute
} from '../declare/type';

const shapeAttribute = {
  x: 0,
  y: 0,
  fillStyle: 'none',
  strokeStyle: 'none',
  lineCap: LineCapType.Round,
  lineDashOffset: 0,
  lineJoin: LineJoinType.Round,
  lineWidth: 1,
  shadowBlur: 0,
  shadowColor: 'none',
  shadowOffsetX: 0,
  shadowOffsetY: 0
};

function isCanvasContext2DAttribute (key: string) : boolean {
  return ['fillStyle', 'font', 'globalAlpha', 'globalCompositeOperation', 'imageSmoothingEnabled',
    'lineCap', 'lineDashOffset', 'lineJoin', 'lineWidth', 'miterLimit', 'shadowBlur', 'shadowColor',
    'shadowOffsetX', 'shadowOffsetY', 'strokeStyle', 'textAlign', 'textBaseline'].includes(key);
}

abstract class AbstractShape {
  protected shapeStatus: ShapeStatus = ShapeStatus.Show;

  protected animationStatus: ShapeAnimationStatus = ShapeAnimationStatus.Ready;

  protected attribute: AbstractShapeAttribute;

  protected readonly id: string = '';

  constructor (id: string, attribute: AbstractShapeOptions) {
    this.id = id;
    this.attribute = Object.assign(shapeAttribute, attribute);
  }

  public getId () : string {
    return this.id;
  }

  public setXY (x: number = 0, y: number = 0) {
    this.attribute.x = x;
    this.attribute.y = y;
    return this;
  }

  public setFillStyle (fillStyle: string = '#000') {
    this.attribute.fillStyle = fillStyle;
    return this;
  }

  public setStrokeStyle (strokeStyle: string = '#000') {
    this.attribute.strokeStyle = strokeStyle;
    return this;
  }

  public setLineWidth (lineWidth: number = 1) {
    this.attribute.lineWidth = lineWidth;
    return this;
  }

  public setShadow (
    shadowBlur: number = 0,
    shadowOffsetX: number = 0,
    shadowOffsetY: number = 0,
    shadowColor: string = '#000'
  ) {
    this.attribute.shadowBlur = shadowBlur;
    this.attribute.shadowOffsetX = shadowOffsetX;
    this.attribute.shadowOffsetY = shadowOffsetY;
    this.attribute.shadowColor = shadowColor;
    return this;
  }

  // eslint-disable-next-line class-methods-use-this
  public applyAttrsToContext (ctx: CanvasRenderingContext2D, attr: AbstractShapeAttribute) {
    Object.keys(attr).forEach((key) => {
      const value = attr[key];
      if (value === undefined || value === 'none') return;
      if (isCanvasContext2DAttribute(key)) {
        // @ts-ignore
        ctx[<CanvasContext2DAttribute>key] = value;
      }
    });
  }

  // eslint-disable-next-line class-methods-use-this
  protected strokeAndFill (ctx: CanvasRenderingContext2D, attr: AbstractShapeAttribute) {
    if (attr.strokeStyle && attr.strokeStyle !== 'none') {
      ctx.fill();
    }
    if (attr.fillStyle && attr.fillStyle !== 'none') {
      ctx.fill();
    }
  }

  protected drawShape (
    ctx: CanvasRenderingContext2D,
    attr: AbstractShapeAttribute,
    drawShapePathCallback: Function
  ) : void {
    ctx.save();
    this.applyAttrsToContext(ctx, attr);
    ctx.beginPath();
    if (drawShapePathCallback) drawShapePathCallback.call(this);
    ctx.closePath();
    this.strokeAndFill(ctx, attr);
    ctx.restore();
  }

  abstract getAttr () : AbstractShapeAttribute;

  abstract draw (ctx: CanvasRenderingContext2D) : void;

  abstract assertHit (x: number, y: number) : boolean;

  /*
  abstract animate () : void;

  abstract animatePaused () : void;

  public destroyed () {

  }

  public paused () {

  }
   */
}

export default AbstractShape;
